/* * Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de> * Copyright (C) 2015 Koji Lin <koji.lin@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.kaif.mobile.kmark; import java.util.ArrayList; import java.util.List; import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.BackgroundColorSpan; import android.text.style.LeadingMarginSpan; import android.text.style.QuoteSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import io.kaif.mobile.kmark.text.BulletSpan2; import io.kaif.mobile.kmark.text.CodeBlockSpan; import io.kaif.mobile.kmark.text.SuperscriptSpan2; /** * Decorator for android spanned * * @author koji lin <koji.lin@gmail.com> */ public class DefaultDecorator implements Decorator { private static class NestSpanInfo { int start; int end; int flag; Object span; public NestSpanInfo(Object span, int start, int end, int flag) { this.start = start; this.end = end; this.flag = flag; this.span = span; } } private int leading; private int bulletGap; private int bulletRadius; private int codeBackgroundColor; public DefaultDecorator(int leading, int bulletGap, int bulletRadius, int codeBackgroundColor) { this.leading = leading; this.bulletGap = bulletGap; this.bulletRadius = bulletRadius; this.codeBackgroundColor = codeBackgroundColor; } @Override public void openParagraph(SpannableStringBuilder out) { appendParagraphNewLines(out); } private void appendParagraphNewLines(SpannableStringBuilder out) { int len = out.length(); if (len >= 1 && out.charAt(len - 1) == '\n') { if (len >= 2 && out.charAt(len - 2) == '\n') { return; } out.append("\n"); return; } if (out.length() != 0) { out.append("\n\n"); } } @Override public void closeParagraph(SpannableStringBuilder out) { } @Override public void openCodeBlock(SpannableStringBuilder out) { start(out, new CodeBlock()); } @Override public void closeCodeBlock(SpannableStringBuilder out) { end(out, CodeBlock.class, new CodeBlockSpan(codeBackgroundColor)); } @Override public void openCodeSpan(SpannableStringBuilder out) { start(out, new Code()); } @Override public void closeCodeSpan(SpannableStringBuilder out) { end(out, Code.class, new BackgroundColorSpan(codeBackgroundColor)); } @Override public void openStrong(SpannableStringBuilder out) { start(out, new Bold()); } @Override public void closeStrong(SpannableStringBuilder out) { end(out, Bold.class, new StyleSpan(Typeface.BOLD)); } @Override public void openStrike(SpannableStringBuilder out) { start(out, new Strike()); } @Override public void closeStrike(SpannableStringBuilder out) { end(out, Strike.class, new StrikethroughSpan()); } @Override public void openEmphasis(SpannableStringBuilder out) { start(out, new Italic()); } @Override public void closeEmphasis(SpannableStringBuilder out) { end(out, Italic.class, new StyleSpan(Typeface.ITALIC)); } @Override public void openSuper(SpannableStringBuilder out) { start(out, new Super()); } @Override public void closeSuper(SpannableStringBuilder out) { end(out, Super.class, new SuperscriptSpan2()); } @Override public void openOrderedList(SpannableStringBuilder out) { appendParagraphNewLines(out); start(out, new OrderedList()); } @Override public void closeOrderedList(SpannableStringBuilder out) { out.removeSpan(getLast(out, OrderedList.class)); } @Override public void openUnorderedList(SpannableStringBuilder out) { appendParagraphNewLines(out); } @Override public void closeUnorderedList(SpannableStringBuilder out) { } @Override public void openOrderedListItem(SpannableStringBuilder out) { appendParagraphNewLines(out); start(out, new OrderedListItem()); } @Override public void closeOrderedListItem(SpannableStringBuilder out) { OrderedList orderedList = getLast(out, OrderedList.class); if (orderedList != null) { int number = orderedList.getAndIncrement(); int where = out.getSpanStart(getLast(out, OrderedListItem.class)); out.insert(where, Integer.toString(number) + ". "); } //check BulletSpan2 end(out, OrderedListItem.class, new LeadingMarginSpan.LeadingMarginSpan2.Standard(leading)); } @Override public void openUnOrderedListItem(SpannableStringBuilder out) { appendParagraphNewLines(out); start(out, new UnOrderedListItem()); } @Override public void closeUnOrderedListItem(SpannableStringBuilder out) { end(out, UnOrderedListItem.class, new BulletSpan2(leading, bulletGap, bulletRadius)); } @Override public void openLink(SpannableStringBuilder out) { start(out, new Link()); } @Override public void closeLink(SpannableStringBuilder out, String url) { end(out, Link.class, new URLSpan(url)); } @Override public void openBlockquote(SpannableStringBuilder out) { start(out, new Blockquote()); } @Override public void closeBlockquote(SpannableStringBuilder out) { end(out, Blockquote.class, new QuoteSpan()); } private static <T> T getLast(Spanned text, Class<T> kind) { T[] objs = text.getSpans(0, text.length(), kind); if (objs.length == 0) { return null; } else { return objs[objs.length - 1]; } } private static void start(SpannableStringBuilder text, Object mark) { int len = text.length(); text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK); } private static <T> void end(SpannableStringBuilder text, Class<T> kind, Object repl) { int len = text.length(); T obj = getLast(text, kind); int where = text.getSpanStart(obj); text.removeSpan(obj); Object[] nestSpans = text.getSpans(where, len, Object.class); List<NestSpanInfo> spans = new ArrayList<>(); for (Object nestSpan : nestSpans) { int spanStart = text.getSpanStart(nestSpan); int spanEnd = text.getSpanEnd(nestSpan); int spanFlags = text.getSpanFlags(nestSpan); text.removeSpan(nestSpan); spans.add(new NestSpanInfo(nestSpan, spanStart, spanEnd, spanFlags)); } if (where != len) { text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } //reorder spans for (NestSpanInfo span : spans) { text.setSpan(span.span, span.start, span.end, span.flag); } } private static class Bold { } private static class Strike { } private static class Italic { } private static class Blockquote { } private static class Super { } private static class Code { } private static class Link { } private static class UnOrderedListItem { } private static class OrderedListItem { } private static class CodeBlock { } private static class OrderedList { private int index; OrderedList() { this.index = 1; } int getAndIncrement() { int current = index; index += 1; return current; } } }